כמה אנחנו שמים על המאזניים כשאנו מתלבטים בין מהירות לנוחות הקוד?
הגיע הזמן לגלות.
אין לאף אחד ספק ש ORM היא הדרך הנוחה ביותר לעבודה עם גאטהבייס,
להתייחס למידע מהמסד כאובייקט ולבצע פעולות בצורה פשוטה ונוחה..
ה ORM שבחרתי, הוא ה php activerecord, והוא מבוסס PDO.
ומה לגבי PDO/MySqli?
ואם היינו משתמשים ב NoSQL?
ברור מתחושה שיש פער בביצועים, אבל עד כמה?
אני לא הצלחתי למצוא השוואה בין כולם בשום מקום, אז החלטתי לנסות בעצמי ולשתף אתכם חברים,
אז קדימה!
השרת בו אני משתמש הוא שרת מסוג m1.large של אמאזון, הנה פרטיו:
M1 Large Instance 7.5 GiB of memory, 4 EC2 Compute Units (2 virtual cores with 2 EC2 Compute Units each), 850 GB of local instance storage, 64-bit platform
נתחיל מלהכניס 20000 שורות לדאטהבייס..
INSERTS
ORM
הנה הקוד בו השתמשתי: (אל תשכחו להוריד את ה active record)
require_once 'php-activerecord/ActiveRecord.php';
class User extends ActiveRecord\Model { }
ActiveRecord\Config::initialize(function($cfg)
{
$cfg->set_model_directory('.');
$cfg->set_connections(array('development' => 'mysql://root:@127.0.0.1/ormtest'));
});
$loops = 20000;
for($i=0;$i<$loops;$i++){
$user = new User();
$user->money = 100;
$user->save();
}
class User extends ActiveRecord\Model { }
ActiveRecord\Config::initialize(function($cfg)
{
$cfg->set_model_directory('.');
$cfg->set_connections(array('development' => 'mysql://root:@127.0.0.1/ormtest'));
});
$loops = 20000;
for($i=0;$i<$loops;$i++){
$user = new User();
$user->money = 100;
$user->save();
}
התוצאה שקיבלתי:
with orm it took 31.54630112648 seconds
*ראו הערה בסוף הכתבה
MySqli
מה אתם חושבים יקרה עם Mysqli? גם אני סקרן..
נרוקן את הטבלה וננסה..
נכניס 20000 שורות,
הנה הקוד בו השתמשתי:
$mysqli = new mysqli("localhost", "root", "", "ormtest");
$stmt = $mysqli->prepare("INSERT INTO users(money) VALUES (?)");
$money = 100;
$loops = 20000;
for($i=0;$i<$loops;$i++){
$stmt->bind_param('i', $money);
$stmt->execute();
}
$stmt = $mysqli->prepare("INSERT INTO users(money) VALUES (?)");
$money = 100;
$loops = 20000;
for($i=0;$i<$loops;$i++){
$stmt->bind_param('i', $money);
$stmt->execute();
}
התוצאה שקיבלתי:
with mysqli it took 24.606364011765 seconds
ניסיתי גם בלי Prepare, בצורה הבאה:
$mysqli->query("INSERT INTO users(money) VALUES (100)");
והתוצאה הייתה:
with mysqli it took 25.704789161682 seconds
גם זה מעניין! כי ה prepare היה אמור לגרום ל Mysql לדלג על אחד השלבים שלו: יצירת החתימה של השאילתה.
PDO
אז השתמשתי ב PDO שכולנו מכירים, הנה הקוד:
$db = new PDO('mysql:host=localhost;dbname=ormtest', 'root', '');
$stmt = $db->prepare("INSERT INTO users(money) VALUES (?)");
$loops = 20000;
for($i=0;$i<$loops;$i++){
$stmt->execute(array(100));
}
$stmt = $db->prepare("INSERT INTO users(money) VALUES (?)");
$loops = 20000;
for($i=0;$i<$loops;$i++){
$stmt->execute(array(100));
}
מסקרן? אז הנה התוצאה:
with PDO it took 26.245838165283 seconds
מדהים, זהה כמעט לחלוטין ל Mysqli, ההימור שלי היה מעט יותר גבוהה
קבלו בונוס, התקנתי היום couchbase, מסד נתונים מסוג Nosql, שיודע לשלב עבודה חכמה עם הram ועוד הרבה דברים, זה סיפור לפעם אחרת, אבל בואו ננסה לבצע עליו את אותן הפעולות.. מה דעתכם?
CouchBase
הנה הקוד בו השתמשתי:
$cb = new Couchbase("localhost:8091", "", "", "default");
for($i=0;$i<20000;$i++){
$cb->set($i, json_encode(array('money'=>100)));
}
for($i=0;$i<20000;$i++){
$cb->set($i, json_encode(array('money'=>100)));
}
with couchbase it took 2.8949840068817 seconds
וואו!!!
כל מילה מיותרת! מהו couchbase ומה עקרונותיו זה כבר לפוסט אחר..
UPDATES
טוב, בואו נעבור לעדכונים! UPDATES!
מה אתם אומרים? הקרב הוכרע? חכו!
אנו נוציא את 20000 השורות, ונוסיף עוד 100 כסף לכל משתמש
ORM
הנה הקוד בו השתמשתי:
require_once 'php-activerecord/ActiveRecord.php';
class User extends ActiveRecord\Model { }
ActiveRecord\Config::initialize(function($cfg)
{
$cfg->set_model_directory('.');
$cfg->set_connections(array('development' => 'mysql://root:@127.0.0.1/ormtest'));
});
$users = User::all();
foreach($users as $key=>$user){
$user->money += 100;
$user->save();
}
class User extends ActiveRecord\Model { }
ActiveRecord\Config::initialize(function($cfg)
{
$cfg->set_model_directory('.');
$cfg->set_connections(array('development' => 'mysql://root:@127.0.0.1/ormtest'));
});
$users = User::all();
foreach($users as $key=>$user){
$user->money += 100;
$user->save();
}
כמובן יכולתי לעשות את זה בשאילתה אחת, אבל זו לא המטרה...
והתוצאה היא:
with orm it took 36.750089883804 seconds
לא שונה בהרבה מלהכניס את כל הנתונים מחדש...
נחזיר לכולם חזרה ל100 כסף וננסה את mysqli
MySqli
הקוד בו השתמשתי:
$mysqli = new mysqli("localhost", "root", "", "ormtest");
$users = $mysqli->query("SELECT * FROM usersorm");
while($user = mysqli_fetch_assoc($users)){
$mysqli->query("UPDATE usersorm SET money=money + 100 WHERE id='{$user['id']}'");
}
$users = $mysqli->query("SELECT * FROM usersorm");
while($user = mysqli_fetch_assoc($users)){
$mysqli->query("UPDATE usersorm SET money=money + 100 WHERE id='{$user['id']}'");
}
with mysqli it took 25.470515966415 seconds
PDO
אז הנה הקוד, שמשתמש ב PDO,
$db = new PDO('mysql:host=localhost;dbname=ormtest', 'root', '');
$sth = $db->prepare("SELECT * FROM users");
$sth->execute();
$users = $sth->fetchAll();
$u = $db->prepare("UPDATE users SET money=money + 100 WHERE id=:uid");
foreach($users as $key=>$user){
$u->bindParam(':uid', $user['id'], PDO::PARAM_INT);
$u->execute();
}
$sth = $db->prepare("SELECT * FROM users");
$sth->execute();
$users = $sth->fetchAll();
$u = $db->prepare("UPDATE users SET money=money + 100 WHERE id=:uid");
foreach($users as $key=>$user){
$u->bindParam(':uid', $user['id'], PDO::PARAM_INT);
$u->execute();
}
ולהלן התוצאה:
with PDO it took 30.656561136246 seconds
טיפה יותר זמן מ mysqli.
Couchbase
אני אפילו אעשה את זה בצורה מעט בזבזנית, כדי לבצע שאילתות מסננות, צריך להשתמש ב map reduce,
בגלל שגם זה לנושא אחר, נעשה את זה בצורה הזו:
$cb = new Couchbase("localhost:8091", "", "", "default");
for($i=0;$i<20000;$i++){
$u = json_decode($cb->get($i));
$cb->set($i,json_encode(array('money'=>$u->money + 100)));
}
for($i=0;$i<20000;$i++){
$u = json_decode($cb->get($i));
$cb->set($i,json_encode(array('money'=>$u->money + 100)));
}
והתוצאה היא....
With couchbase it took 5.4407348632812 seconds
וואו! עשיתי פה 40000 פעולות, 20000 קריאה, 20000 כתיבה (כפול משאר הבדיקות, ותראו מה קיבלנו!)
*הערה חשובה מאוד
ביצעתי את הניסוי גם על המחשב האישי שלי, שחלש בהרבה מהשרת,
הבדל אחד היה וזה בין ORM ל mysqli/PDO,
כאשר בבדיקה הנוכחית השתמשתי בשרת חזק, הפערים היו לא גדולים,
כאשר השתמשתי בשרת חלש, הORM לקח פי 3 יותר זמן!
לקח לי 55 שניות להכניס 2500 שורות ב mysqli, לעומת 155 שניות להכניס 2500 שורות עם activerecord.
ככל שהשרת חזק יותר הפערים מצטמצמים, אז מאוד חשוב לקחת את זה בחשבון.
לגבי השאר התוצאות בשרת חלש יותר היו ביחס זהה לתוצאות כאן.
מסקנות
גם תוצאה לא כל כך מפתיעה, אבל אכן פערים משמעותיים,
האם הנוחות של ORM שווה את זה? (תלוי בפרוייקט), ולפי ההערה תלוי בעוצמת השרת שלכם,
האם הוא יכול להינות מהלוקסוס הזה.
כמובן בשאילתות בודדות, ההבדל יהיה זניח, וקוד קריא וברור לפעמים חשוב לא פחות,
בשילוב מנגוני cache כמו memcached או apc אפשר לצמצם את העניין בקריאת נתונים.
אבל ראיתם מה קורה כשרוצים להריץ 20000 במכה אחת.
אז באמת שזה תלוי בפרוייקט, באיכות השרת או בסוג המידע בו אתם משתמשים.
אז תהיו בטוחים מה אתם עושים כשאתם בוחרים ב ORM.
לגבי PDO/MySQLi זה ברור, תבחרו PDO, כל יתרונותיו עולים על השנייה הנוספת.
ומה למדנו לגבי nosql ו couchbase בפרט?
כשאפשר, לכו.. אהה סליחה, טוסו על זה!!
ראינו שבאופן אירוני, ככל שהקוד יפה יותר, נוח יותר, כך הוא רץ לאט יותר,
חבל שלא נולדנו רובוטים,
אבל תמיד אפשר לפתח כלים, לחשוב יצירתי, ולהגיע לביצועים מדהימים.
ומה אתם חושבים על זה?
תגובות לכתבה:
תודה רבה, השוואה מעניינת בהחלט.
התוצאות מעניינות אבל לא מפתיעות בכלל.
חשוב לציין שהטייטל עצמו יכול להיות קצת מבלבל (orm vs mysqli) , בגלל שה-ORM עצמו יכול להיות מבוסס על גבי mysqli.
ההשוואה ההגינית תהיה בין שאילתות פשוטות ב PDO לבין ORM שיושב על גבי PDO.
בכל אופן, התוצאות שם לא יהיו שונות בהרבה.
מה שכן מעניין, הוא שהוצגו שני שאילתות מאוד מאוד פשוטות.
ברגע שמגיעים לשליפות מכמה טבלאות או שליפות מתוחכמות, כמו לשלוף את כל ההודעות באשכול הזה ולשלוף את המידע הבסיסי על המשתמשים שכתבו את ההודעות האלה - ORM נותן תוצאות הרבה יותר גרועות בגלל שהוא לא באמת יודע מה אתה בסופו של דבר צריך.
האם לשלוף הכל בשאילת אחת עם join, האם לשלוף בשני שאילתות, אחת לאשכול ואחת לכמה משתמשים בבת אחת או לשעות 10 שאילתות ל 10 תשובות באשכול ועשרת המתשמשים שהשאירו אותם (וזה מה שיקרה ברוב המקרים).
----
אגב, בדוגמה השניה בכתבה ה ORM קודם ישלוף את השורות אחרי זה יוסיף לכל אחת מהן 100 ואחרי זה ישמור אותם. כלומר יוצר 2500 שאילתות קריאה ועוד 2500 שאילתות עדכון.
במקרה עם השאילה הישירה אין שליפה. יש רק 2500 שאילתות עדכון וזהו.
רוב ה ORMים מאפשרים לעשות שאילתות עדכון בלי שליפה מקדימה.
בקשר לדוגמא השניה, הוא יוצר שאילתת קריאה אחת, הוא מוציא את כל המשתמשים,
הORM הזה יודע לעשות כמובן את העידכון בשאילתה אחת (ואף כתבתי את זה)
אבל במקרה וארצה להוסיף סכום שונה לכל אחד וכו'..
המטרה הייתה אותן כמות שאילתות בשתי הבדיקות.
active record כתוב על בסיס PDO, מה שכן, יש הרבה השוואות בין mysqli ל PDO.
מקווה שניהניתם.
אגב, לגבי joins ב ORM, אז זה מאוד פשוט, וכן אפשר להגדיר את רוב הדברים שאתה רוצה.
אני מניח שגם שם התוצאות מהירות יהיו דומות, או קשות יותר.
מה עם Eloquent ORM של לארוול? D:
אתה יכול לעשות את הבדיקה על eloquent לבד, אבל התוצאות יהיו זהות.
תודה רבה :)
אני אשאר בPDO ...
אני מאוד מאמין ש
>> אני אשאר בPDO
היא טעות מאוד נפוצה שמתכנתים עושים כאשר הם מעדיפים עוד 10 מילישניות של השרת על פני 10 דקות עבודה שלהם.
אני יודע שתכף יבואו אנשים ויגידו שאני אמרתי לכתוב קוד גרוע ואיטי
אבל המשפט הזה אומר משהו אחר לגמרי:
המשפט הזה אומר שב 90% מהמקרים השקעת הזמן של המתכנת היא לא נכונה.
היא לא צריכה להתבזבז על נסיון לבנות שאילת אידאלית, אלה צריכה להיות מושקעת בבנית ערך מוסף לאפליקציה.
הרגע היחידי שמותר בו למתכנת להתחיל לעסוק ביעול זה רק אחרי שהמתכנת הפעיל את הפרופיילר וראה לבד מה לוקח הרבה זמן וגילה שבפועל לא השלוש שאילתות שיש בעמוד הוסיפו 30 מילישניות לזמן הטעינה,
אלה הלולאה בתוך לולאה והקריאה לפונקציה מיותרת איפשהו לפני גרמול לאיטיות.
תנשה לעשות query עם הPDO ככה הוא עובד מהר יותר
תכלס אתה צודק.
צריך להשוות prepared statements ל prepared statements
ו query ל query